<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js 粒子热力图</title>
    <style>
        body { margin: 0; overflow: hidden; }
        canvas { display: block; }
        #info {
            position: absolute;
            top: 10px;
            width: 100%;
            text-align: center;
            color: white;
            font-family: Arial, sans-serif;
            pointer-events: none;
        }
    </style>
</head>
<body>
    <div id="info">Three.js 粒子热力图<br>鼠标拖动旋转，滚轮缩放</div>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.min.js"></script>
    <script>
        // 初始化场景、相机和渲染器
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x111111);
        
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.z = 3;
        
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(window.devicePixelRatio);
        document.body.appendChild(renderer.domElement);
        
        // 添加轨道控制器
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        
        // 窗口大小调整
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
        
        // 创建热力数据
        const createHeatData = (size) => {
            const data = new Float32Array(size * size * size);
            
            // 创建几个球形热源
            const spheres = [
                { center: [0.3, 0.3, 0.3], radius: 0.2, intensity: 1.0 },
                { center: [0.7, 0.7, 0.3], radius: 0.15, intensity: 0.8 },
                { center: [0.5, 0.5, 0.7], radius: 0.25, intensity: 1.2 },
                { center: [0.2, 0.8, 0.5], radius: 0.18, intensity: 0.9 },
                { center: [0.8, 0.2, 0.5], radius: 0.22, intensity: 1.1 }
            ];
            
            for (let z = 0; z < size; z++) {
                for (let y = 0; y < size; y++) {
                    for (let x = 0; x < size; x++) {
                        const nx = x / (size - 1);  // 归一化到[0,1]
                        const ny = y / (size - 1);
                        const nz = z / (size - 1);
                        
                        let density = 0;
                        
                        // 计算每个球体的贡献
                        for (const sphere of spheres) {
                            const dx = nx - sphere.center[0];
                            const dy = ny - sphere.center[1];
                            const dz = nz - sphere.center[2];
                            const distance = Math.sqrt(dx*dx + dy*dy + dz*dz);
                            
                            if (distance < sphere.radius) {
                                const falloff = Math.pow(1.0 - distance/sphere.radius, 2);
                                density += sphere.intensity * falloff;
                            }
                        }
                        
                        // 添加一些噪声
                        const noise = 0.05 * Math.random();
                        data[z * size * size + y * size + x] = Math.min(1.0, density + noise);
                    }
                }
            }
            
            return data;
        };
        
        // 创建粒子系统
        const createParticleSystem = (size, heatData) => {
            const particleCount = size * size * size;
            const particles = new THREE.BufferGeometry();
            
            // 创建粒子位置
            const positions = new Float32Array(particleCount * 3);
            const colors = new Float32Array(particleCount * 3);
            const sizes = new Float32Array(particleCount);
            
            let i = 0, j = 0;
            for (let z = 0; z < size; z++) {
                for (let y = 0; y < size; y++) {
                    for (let x = 0; x < size; x++) {
                        // 位置 [-0.5, 0.5]
                        positions[i] = (x / (size - 1)) - 0.5;
                        positions[i + 1] = (y / (size - 1)) - 0.5;
                        positions[i + 2] = (z / (size - 1)) - 0.5;
                        
                        // 根据热力值设置颜色
                        const heatValue = heatData[z * size * size + y * size + x];
                        
                        if (heatValue > 0.1) {
                            // 热力区域 - 热力图颜色
                            if (heatValue < 0.3) {
                                // 蓝色到青色
                                colors[j] = 0;
                                colors[j + 1] = heatValue * 3.3;
                                colors[j + 2] = 0.5 + heatValue;
                            } else if (heatValue < 0.6) {
                                // 青色到黄色
                                colors[j] = (heatValue - 0.3) * 3.3;
                                colors[j + 1] = 1.0;
                                colors[j + 2] = 1.0 - (heatValue - 0.3) * 3.3;
                            } else {
                                // 黄色到红色
                                colors[j] = 1.0;
                                colors[j + 1] = 1.0 - (heatValue - 0.6) * 2.5;
                                colors[j + 2] = 0;
                            }
                        } else {
                            // 非热力区域 - 绿色
                            colors[j] = 0;
                            colors[j + 1] = 0.5;
                            colors[j + 2] = 0;
                        }
                        
                        // 粒子大小根据热力值变化
                        sizes[z * size * size + y * size + x] = heatValue > 0.1 ? 0.05 + heatValue * 0.1 : 0.03;
                        
                        i += 3;
                        j += 3;
                    }
                }
            }
            
            particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
            particles.setAttribute('color', new THREE.BufferAttribute(colors, 3));
            particles.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
            
            // 创建粒子材质
            const particleMaterial = new THREE.PointsMaterial({
                size: 0.01,
                vertexColors: true,
                transparent: true,
                opacity: 0.8,
                sizeAttenuation: true
            });
            
            // 创建粒子系统
            const particleSystem = new THREE.Points(particles, particleMaterial);
            return particleSystem;
        };
        
        const volumeSize = 40;  // 粒子数量为20x20x20=8000个
        const heatData = createHeatData(volumeSize);
        const particleSystem = createParticleSystem(volumeSize, heatData);
        scene.add(particleSystem);
        
        // 添加坐标轴辅助
        const axesHelper = new THREE.AxesHelper(1.5);
        scene.add(axesHelper);
        
        // 添加GUI控制
        const gui = new dat.GUI();
        const params = {
            particleSize: 0.05,
            opacity: 0.8,
            heatThreshold: 0.1
        };
        
        gui.add(params, 'particleSize', 0.01, 0.2).name('粒子大小').onChange((value) => {
            particleSystem.material.size = value;
        });
        
        gui.add(params, 'opacity', 0.1, 1).name('透明度').onChange((value) => {
            particleSystem.material.opacity = value;
        });
        
        gui.add(params, 'heatThreshold', 0, 0.5).name('热力阈值').onChange((value) => {
            // 更新粒子颜色和大小
            const colors = particleSystem.geometry.attributes.color.array;
            const sizes = particleSystem.geometry.attributes.size.array;
            
            for (let i = 0; i < heatData.length; i++) {
                const heatValue = heatData[i];
                const j = i * 3;
                
                if (heatValue > value) {
                    // 热力区域 - 热力图颜色
                    if (heatValue < 0.3) {
                        colors[j] = 0;
                        colors[j + 1] = heatValue * 3.3;
                        colors[j + 2] = 0.5 + heatValue;
                    } else if (heatValue < 0.6) {
                        colors[j] = (heatValue - 0.3) * 3.3;
                        colors[j + 1] = 1.0;
                        colors[j + 2] = 1.0 - (heatValue - 0.3) * 3.3;
                    } else {
                        colors[j] = 1.0;
                        colors[j + 1] = 1.0 - (heatValue - 0.6) * 2.5;
                        colors[j + 2] = 0;
                    }
                    sizes[i] = 0.05 + heatValue * 0.1;
                } else {
                    // 非热力区域 - 绿色
                    colors[j] = 0;
                    colors[j + 1] = 0.5;
                    colors[j + 2] = 0;
                    sizes[i] = 0.03;
                }
            }
            
            particleSystem.geometry.attributes.color.needsUpdate = true;
            particleSystem.geometry.attributes.size.needsUpdate = true;
        });
        
        // 动画循环
        function animate() {
            requestAnimationFrame(animate);
            
            // 添加一些动画效果 - 让粒子轻微浮动
            const positions = particleSystem.geometry.attributes.position.array;
            const time = Date.now() * 0.0005;
            
            for (let i = 0; i < positions.length; i += 3) {
                const heatValue = heatData[Math.floor(i / 3)];
                if (heatValue > params.heatThreshold) {
                    // 热力粒子有更明显的浮动
                    positions[i] += Math.sin(time + i) * 0.003 * heatValue;
                    positions[i + 1] += Math.cos(time + i * 0.5) * 0.003 * heatValue;
                    positions[i + 2] += Math.sin(time * 0.7 + i * 0.3) * 0.003 * heatValue;
                } else {
                    // 绿色背景粒子轻微浮动
                    positions[i] += Math.sin(time + i) * 0.001;
                    positions[i + 1] += Math.cos(time + i * 0.5) * 0.001;
                    positions[i + 2] += Math.sin(time * 0.7 + i * 0.3) * 0.001;
                }
            }
            
            particleSystem.geometry.attributes.position.needsUpdate = true;
            
            controls.update();
            renderer.render(scene, camera);
        }
        
        animate();
    </script>
</body>
</html>